package pl.edu.agh.pros.proxy;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;

import org.jivesoftware.smack.XMPPException;
import org.ros.node.NodeConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import pl.edu.agh.pros.conf.ProxyConfiguration;
import pl.edu.agh.pros.conf.XMPPConfiguration;
import pl.edu.agh.pros.connector.IServerConnector;
import pl.edu.agh.pros.internal.proxy.ProxyDispatcher;
import pl.edu.agh.pros.internal.proxy.service.ProxyServiceReceiver;
import pl.edu.agh.pros.internal.proxy.service.ProxyServiceSender;
import pl.edu.agh.pros.internal.proxy.topic.ProxyTopicReceiver;
import pl.edu.agh.pros.internal.proxy.topic.ProxyTopicSender;
import pl.edu.agh.pros.proxy.handler.ProxyServiceReceiverHandler;
import pl.edu.agh.pros.proxy.handler.ProxyServiceSenderHandler;
import pl.edu.agh.pros.proxy.handler.ProxyTopicReceiverHandler;
import pl.edu.agh.pros.renewal.ExecutionTask;
import pl.edu.agh.pros.renewal.PeriodicalExecutorFactory;
import pl.edu.agh.pros.xmpp.XMPPManager;

public final class RosProxy implements IRosProxy {

	private static final double REGISTRATION_DIVIDER = 2.1;

	private static final Logger logger = LoggerFactory.getLogger(RosProxy.class);

	private CountDownLatch latch = new CountDownLatch(1);
	private boolean started = false;

	private final String appKey;
	private final String devId;
	private final NodeConfiguration nodeConfiguration;
	private final IServerConnector serverConnector;
	private boolean isProvider;

	private XMPPConfiguration xmppConfiguration;
	private XMPPManager xmppManager;

	private ProxyDispatcher proxyDispatcher;
	private String providerXmppAddress;

	private Set<RosProxyHandler> topicHandlers = new HashSet<RosProxyHandler>();

	private RosProxy(String appKey, String devId, NodeConfiguration nodeConfiguration, IServerConnector serverConnector) {
		this.appKey = appKey;
		this.devId = devId;
		this.nodeConfiguration = nodeConfiguration;
		this.serverConnector = serverConnector;
	}

	public static RosProxy newInstance(String appKey, String devId, NodeConfiguration nodeConfiguration,
			IServerConnector serverConnector) throws XMPPException {
		return new RosProxy(appKey, devId, nodeConfiguration, serverConnector);
	}

	public void start() throws XMPPException {
		xmppConfiguration = serverConnector.getXMPPConfiguration(appKey, devId);
		if (xmppConfiguration == null) {
			logger.error("Could not retrive XMPP configuration. Make sure that server address is correct."); //$NON-NLS-1$
			logger.error("Exiting RosProxy."); //$NON-NLS-1$
			latch.countDown();
			return;
		}
		init(xmppConfiguration);
	}

	public void awaitStart() {
		if (started) {
			return;
		}
		try {
			latch.await();
		} catch (InterruptedException e) {
			// ignore
		}
	}

	public void stop() {
		PeriodicalExecutorFactory.stopPeriodicalExecutors();
		if (xmppManager != null) {
			xmppManager.disconnect();
		}
		if (proxyDispatcher != null) {
			proxyDispatcher.dispose();
		}
		proxyDispatcher = null;
	}

	public void reconnect() {
		if (xmppManager != null) {
			xmppManager.reconnect();
		}
		for (RosProxyHandler rph : topicHandlers) {
			rph.stop();
			rph.start();
			rph.awaitStart();
		}
	}

	public void restart() throws XMPPException {
		stop();
		init(xmppConfiguration);
	}

	public RosProxyHandler createProxyForTopicSubscriber(ProxyConfiguration proxyConfiguration) {
		ProxyTopicReceiver proxyTopicReceiver = new ProxyTopicReceiver(proxyDispatcher, proxyConfiguration);
		RosProxyHandler proxyHandler = new ProxyTopicReceiverHandler(proxyTopicReceiver, nodeConfiguration);
		topicHandlers.add(proxyHandler);
		return proxyHandler;

	}

	public RosProxyHandler createProxyForTopicPublisher(ProxyConfiguration proxyConfiguration) {
		ProxyTopicSender proxyTopicSender = new ProxyTopicSender(proxyDispatcher, proxyConfiguration);
		RosProxyHandler proxyHandler = new ProxyTopicReceiverHandler(proxyTopicSender, nodeConfiguration);
		topicHandlers.add(proxyHandler);
		return proxyHandler;
	}

	public RosProxyHandler createProxyForServiceClient(ProxyConfiguration proxyConfiguration) {
		ProxyServiceSender proxyServiceSender = new ProxyServiceSender(proxyDispatcher, proxyConfiguration, null);
		RosProxyHandler proxyHandler = new ProxyServiceSenderHandler(proxyServiceSender, nodeConfiguration);
		return proxyHandler;
	}

	public RosProxyHandler createProxyForServiceClient(ProxyConfiguration proxyConfiguration, String devId) {
		ProxyServiceSender proxyServiceSender = new ProxyServiceSender(proxyDispatcher, proxyConfiguration, devId);
		RosProxyHandler proxyHandler = new ProxyServiceSenderHandler(proxyServiceSender, nodeConfiguration);
		return proxyHandler;
	}

	public RosProxyHandler createProxyForServiceServer(ProxyConfiguration proxyConfiguration) {
		ProxyServiceReceiver proxyServiceReceiver = new ProxyServiceReceiver(proxyDispatcher, proxyConfiguration);
		RosProxyHandler proxyHandler = new ProxyServiceReceiverHandler(proxyServiceReceiver, nodeConfiguration);
		return proxyHandler;
	}

	private void init(XMPPConfiguration xmppConfiguration) throws XMPPException {
		try {
			xmppManager = XMPPManager.newInstance(xmppConfiguration);
			if (xmppManager == null) {
				latch.countDown();
				return;
			}
			Long registrationDuration = serverConnector.register(appKey, devId, xmppManager.getLoggedUser());
			providerXmppAddress = serverConnector.getXmppAddress(appKey, null);
			PeriodicalExecutorFactory.getPeriodicalExecutor((long) (registrationDuration / REGISTRATION_DIVIDER))
					.addTask(new ExecutionTask() {
						@Override
						public void execute() {
							serverConnector.register(appKey, devId, xmppManager.getLoggedUser());
							logger.debug("User registration renewed [" + devId + ", " + xmppManager.getLoggedUser() //$NON-NLS-1$ //$NON-NLS-2$
									+ "]"); //$NON-NLS-1$
						}
					}, 0);
			isProvider = xmppManager.getLoggedUser().equals(providerXmppAddress);
			proxyDispatcher = new ProxyDispatcher(serverConnector, xmppManager, isProvider, providerXmppAddress, devId,
					appKey);
			started = true;
			latch.countDown();
		} catch (XMPPException e) {
			logger.error("Could not connect to xmppServer", e); //$NON-NLS-1$
			latch.countDown();
			throw e;
		}
	}

}
